#include "StringUtil.h"
#include "bcrypt/aes.h"
#include "bcrypt/base64.h"

#if _WIN32
#include "objbase.h"
#include <stdio.h>
#endif

namespace OCPP
{
	const String StringUtil::GenGUID()
	{
#if _WIN32
		static char buf[64] = { 0 };
		GUID guid;
		if (S_OK == ::CoCreateGuid(&guid))
		{
			_snprintf(buf, sizeof(buf)
				, "%08X-%04X-%04x-%02X%02X-%02X%02X%02X%02X%02X%02X"
				, guid.Data1
				, guid.Data2
				, guid.Data3
				, guid.Data4[0], guid.Data4[1]
				, guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5]
				, guid.Data4[6], guid.Data4[7]
			);
		}
		return String((const char*)buf);
#else
		static unsigned int _uuid = 0;
		StringStream s;
		s << _uuid++;
		return s.str();
#endif
	}

	const String StringUtil::hexEncode(String str)
	{
		static const String hex = "0123456789ABCDEF";
		StringStream result;
		for (size_t i = 0; i < str.length(); ++i)
			result << hex[(unsigned char)str[i] >> 4] << hex[(unsigned char)str[i] & 0xf];
		return result.str();
	}

	const String StringUtil::hexDecode(String hexStr)
	{
		StringStream result;
		char buf[2];
		for (size_t i = 0; i < hexStr.length(); i += 2)
		{
			buf[0] = hexStr[i];
			buf[1] = hexStr[i + 1];
			result << static_cast<unsigned char>(strtoul(buf, NULL, 16));
		}
		return result.str();
	}

	const String StringUtil::encodeBase64(const uint8* in, uint32 inlen)
	{
		String out;
		out.resize(inlen * 2);
		int len = base64_encode(in, inlen, &out[0]);
		return out;
	}

	const String StringUtil::encodeBase64(String str)
	{
		return encodeBase64((const uint8*)str.c_str(), str.length());
	}

	const String StringUtil::decodeBase64(String str)
	{
		String out;
		out.resize(str.size());
		int len = base64_decode((const int8*)str.c_str(), str.length(), (uint8*) & out[0]);
		out.resize(len);
		return out;
	}

	const String StringUtil::encryptAES(String key1, String data)
	{
		String result;
		result.resize(data.length());
		AES aes;
		aes.setMode((unsigned char*)key1.c_str(), (int)key1.length(), data.length(), false);
		aes.cipher((const unsigned char*)data.c_str(), (unsigned char*)result.c_str());
		return result;
	}

	const String StringUtil::decryptAES(String key1, String data)
	{
		String result;
		result.resize(key1.length());
		AES aes;
		aes.setMode((unsigned char*)key1.c_str(), (int)key1.length(), data.length(), true);
		aes.invCipher((const unsigned char*)data.c_str(), (unsigned char*)result.c_str());
		return result;
	}

	String convertUTF16toUTF8(const wchar_t* source, unsigned sourceLength)
	{
#if defined(WIN32) && !defined(__CYGWIN__)
		if (sourceLength == 0)
		{
			return String();
		}

		int destLen = WideCharToMultiByte(CP_UTF8, 0, source, sourceLength, 0, 0, 0, 0);
		if (destLen <= 0)
		{
			return String();
		}

		String sDest(destLen, '\0');
		destLen = WideCharToMultiByte(CP_UTF8, 0, source, sourceLength, &sDest[0], destLen, 0, 0);

		if (destLen <= 0)
		{
			return String();
		}

		return sDest;
#else
		return String();
#endif
	}

	String convertUTF16toUTF8(const WString& s) { return convertUTF16toUTF8(s.c_str(), s.length()); }
	String convertStringFromCurrentCodePageToUTF8(const char* source, unsigned sourceLength)
	{
#if defined(WIN32) && !defined(__CYGWIN__)
		if (sourceLength == 0)
		{
			return String();
		}

		int utf16Length = MultiByteToWideChar(CP_ACP, 0, source, sourceLength, 0, 0);
		if (utf16Length <= 0)
		{
			return String();
		}

		std::wstring sUTF16(utf16Length, L'\0');
		utf16Length = MultiByteToWideChar(CP_ACP, 0, source, sourceLength, &sUTF16[0], utf16Length);
		if (utf16Length <= 0)
		{
			return String();
		}

		return convertUTF16toUTF8(sUTF16);
#else
		return source;
#endif
	}

	std::wstring convertUTF8toUTF16(const char* source, unsigned sourceLength)
	{
#if defined(WIN32) && !defined(__CYGWIN__)
		if (sourceLength == 0)
		{
			return std::wstring();
		}

		int destLen = MultiByteToWideChar(CP_UTF8, 0, source, sourceLength, 0, 0);
		if (destLen <= 0)
		{
			return std::wstring();
		}

		std::wstring sDest(destLen, L'\0');
		destLen = MultiByteToWideChar(CP_UTF8, 0, source, sourceLength, &sDest[0], destLen);

		if (destLen <= 0)
		{
			return std::wstring();
		}

		return sDest;
#else
		//TODO: Implement for other platforms
		return std::wstring();
#endif
	}

	std::string convertStringFromUTF8toCurrentCodePage(const char* source, unsigned sourceLength)
	{
#if defined(WIN32) && !defined(__CYGWIN__)
		if (sourceLength == 0)
		{
			return std::string();
		}

		std::wstring utf16 = convertUTF8toUTF16(source, sourceLength);
		sourceLength = utf16.length();

		int destLen = WideCharToMultiByte(CP_ACP, 0, utf16.c_str(), sourceLength, 0, 0, 0, 0);
		if (destLen <= 0)
		{
			return std::string();
		}

		std::string sDest(destLen, '\0');
		destLen = WideCharToMultiByte(CP_ACP, 0, utf16.c_str(), sourceLength, &sDest[0], destLen, 0, 0);
		if (destLen <= 0)
		{
			return std::string();
		}

		return sDest;
#else
		return source;
#endif
	}

	String StringUtil::UTF8ToANSI(const String& s) { return convertStringFromUTF8toCurrentCodePage(s.c_str(), s.length()); }

	WString UTF8toUTF16(const char* s) { return convertUTF8toUTF16(s, strlen(s)); }

	String StringUtil::ANSIToUTF8(const String str)
	{
		return convertStringFromCurrentCodePageToUTF8(str.c_str(), str.length());
	}

	WString StringUtil::UTF8toUTF16(const String s) { return convertUTF8toUTF16(s.c_str(), s.length()); }

	const String StringUtil::replaceAll(const String& source, const String& replaceWhat, const String& replaceWithWhat)
	{
		String result = source;
		String::size_type pos = 0;
		while (1)
		{
			pos = result.find(replaceWhat, pos);
			if (pos == String::npos) break;
			result.replace(pos, replaceWhat.size(), replaceWithWhat);
			pos += replaceWithWhat.size();
		}
		return result;
	}

	bool StringUtil::startsWith(const String& ref, const String& pattern, bool caseSensitive)
	{
		if (pattern.length() > ref.length())
			return false;

		if (caseSensitive)
		{
			for (unsigned i = 0; i < pattern.length(); ++i)
			{
				if (ref[i] != pattern[i])
					return false;
			}
		}
		else
		{
			for (unsigned i = 0; i < pattern.length(); ++i)
			{
				if (std::toupper(ref[i]) != std::toupper(pattern[i]))
					return false;
			}
		}
		return true;
	}

	bool StringUtil::endsWith(const String& ref, const String& pattern, bool caseSensitive)
	{
		if (pattern.length() > ref.length())
			return false;

		unsigned offset = ref.size() - pattern.length();
		if (caseSensitive)
		{
			for (unsigned i = 0; i < pattern.length(); ++i)
			{
				if (ref[i + offset] != pattern[i])
					return false;
			}
		}
		else
		{
			for (unsigned i = 0; i < pattern.length(); ++i)
			{
				if (std::toupper(ref[i + offset]) != std::toupper(pattern[i]))
					return false;
			}
		}
		return true;
	}

	StringVector StringUtil::split(const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims)
	{
		StringVector ret;
		// Pre-allocate some space for performance
		ret.reserve(maxSplits ? maxSplits + 1 : 10);    // 10 is guessed capacity for most case

		unsigned int numSplits = 0;

		// Use STL methods 
		size_t start, pos;
		start = 0;
		do
		{
			pos = str.find_first_of(delims, start);
			if (pos == start)
			{
				// Do nothing
				start = pos + 1;
				if (pos != 0)
				{
					ret.push_back("");
				}
			}
			else if (pos == String::npos || (maxSplits && numSplits == maxSplits))
			{
				// Copy the rest of the string
				ret.push_back(str.substr(start));
				break;
			}
			else
			{
				// Copy up to delimiter
				ret.push_back(str.substr(start, pos - start));

				if (preserveDelims)
				{
					// Sometimes there could be more than one delimiter in a row.
					// Loop until we don't find any more delims
					size_t delimStart = pos, delimPos;
					delimPos = str.find_first_not_of(delims, delimStart);
					if (delimPos == String::npos)
					{
						// Copy the rest of the string
						ret.push_back(str.substr(delimStart));
					}
					else
					{
						ret.push_back(str.substr(delimStart, delimPos - delimStart));
					}
				}

				start = pos + 1;
			}
			// parse up to next real data
			// start = str.find_first_not_of(delims, start);
			++numSplits;

		} while (pos != String::npos);



		return ret;
	}

	void StringUtil::splitFilename(const String& qualifiedName,
		String& outBasename, String& outPath)
	{
		String path = qualifiedName;
		// Replace \ with / first
		std::replace(path.begin(), path.end(), '\\', '/');
		// split based on final /
		size_t i = path.find_last_of('/');

		if (i == String::npos)
		{
			outPath.clear();
			outBasename = qualifiedName;
		}
		else
		{
			outBasename = path.substr(i + 1, path.size() - i - 1);
			outPath = path.substr(0, i + 1);
		}

	}

	void StringUtil::splitFullFilename(const String& qualifiedName,
		String& outBasename, String& outExtention, String& outPath)
	{
		String fullName;
		splitFilename(qualifiedName, fullName, outPath);
		splitBaseFilename(fullName, outBasename, outExtention);
	}

	void StringUtil::splitBaseFilename(const String& fullName,
		String& outBasename, String& outExtention)
	{
		size_t i = fullName.find_last_of('.');
		if (i == String::npos)
		{
			outExtention.clear();
			outBasename = fullName;
		}
		else
		{
			outExtention = fullName.substr(i + 1);
			outBasename = fullName.substr(0, i);
		}
	}

	void StringUtil::trim(String& str, bool left, bool right, String delims)
	{
		if (right)
			str.erase(str.find_last_not_of(delims) + 1); // trim right
		if (left)
			str.erase(0, str.find_first_not_of(delims)); // trim left
	}

	void StringUtil::toUpperCase(String& str)
	{
		std::transform(
			str.begin(),
			str.end(),
			str.begin(),
			toupper);
	}

	void StringUtil::toLowerCase(String& str)
	{
		std::transform(
			str.begin(),
			str.end(),
			str.begin(),
			tolower);
	}

	double StringUtil::asciiToDouble(const char* str)
	{
		const char* ptr = str;

		// check if could be a hex number.
		if (strncmp(ptr, "0x", 2) == 0)
		{

			double value = 0.0;

			// skip over leading 0x, and then go through rest of string
			// checking to make sure all values are 0...9 or a..f.
			ptr += 2;
			while (
				*ptr != 0 &&
				((*ptr >= '0' && *ptr <= '9') ||
					(*ptr >= 'a' && *ptr <= 'f') ||
					(*ptr >= 'A' && *ptr <= 'F'))
				)
			{
				if (*ptr >= '0' && *ptr <= '9') value = value * 16.0 + double(*ptr - '0');
				else if (*ptr >= 'a' && *ptr <= 'f') value = value * 16.0 + double(*ptr - 'a' + 10);
				else if (*ptr >= 'A' && *ptr <= 'F') value = value * 16.0 + double(*ptr - 'A' + 10);
				++ptr;
			}

			// OSG_NOTICE<<"Read "<<str<<" result = "<<value<<std::endl;
			return value;
		}

		ptr = str;

		bool    hadDecimal[2];
		double  value[2];
		double  sign[2];
		double  decimalMultiplier[2];

		hadDecimal[0] = hadDecimal[1] = false;
		sign[0] = sign[1] = 1.0;
		value[0] = value[1] = 0.0;
		decimalMultiplier[0] = decimalMultiplier[1] = 0.1;
		int pos = 0;

		// compute mantissa and exponent parts
		while (*ptr != 0 && pos < 2)
		{
			if (*ptr == '+')
			{
				sign[pos] = 1.0;
			}
			else if (*ptr == '-')
			{
				sign[pos] = -1.0;
			}
			else if (*ptr >= '0' && *ptr <= '9')
			{
				if (!hadDecimal[pos])
				{
					value[pos] = value[pos] * 10.0 + double(*ptr - '0');
				}
				else
				{
					value[pos] = value[pos] + decimalMultiplier[pos] * double(*ptr - '0');
					decimalMultiplier[pos] *= 0.1;
				}
			}
			else if (*ptr == '.')
			{
				hadDecimal[pos] = true;
			}
			else if (*ptr == 'e' || *ptr == 'E')
			{
				if (pos == 1) break;

				pos = 1;
			}
			else
			{
				break;
			}
			++ptr;
		}

		if (pos == 0)
		{
			// OSG_NOTICE<<"Read "<<str<<" result = "<<value[0]*sign[0]<<std::endl;
			return value[0] * sign[0];
		}
		else
		{
			double mantissa = value[0] * sign[0];
			double exponent = value[1] * sign[1];
			//OSG_NOTICE<<"Read "<<str<<" mantissa = "<<mantissa<<" exponent="<<exponent<<" result = "<<mantissa*pow(10.0,exponent)<<std::endl;
			return mantissa * pow(10.0, exponent);
		}
	}

	double StringUtil::findAsciiToDouble(const char* str)
	{
		const char* ptr = str;
		double value = 0.0;

		while (*ptr != 0) {

			if (*ptr >= '0' && *ptr <= '9') {
				value = asciiToDouble(ptr);
				return value;
			}

			++ptr;
		}

		return 0.0;
	}

	float StringUtil::asciiToFloat(const char* str)
	{
		return static_cast<float>(findAsciiToDouble(str));
	}

	bool StringUtil::contains(const String& str, const String& keyword, bool caseSensitive)
	{
		String tmpStr = str;
		String tmpKeyword = keyword;
		if (!caseSensitive)
		{
			StringUtil::toLowerCase(tmpStr);
			StringUtil::toLowerCase(tmpKeyword);
		}

		return tmpStr.find(tmpKeyword) != tmpStr.npos;
	}

	const String StringUtil::getTime()
	{
		std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
		tm gmtm;
#if WIN32
		localtime_s(&gmtm, &now);
#else
		localtime_r(&now, &gmtm);
#endif
		return str_format("%4d%02d%02d %02d:%02d:%02d",
			1900 + gmtm.tm_year,
			1 + gmtm.tm_mon,
			gmtm.tm_mday,
			gmtm.tm_hour,
			gmtm.tm_min,
			gmtm.tm_sec);
	}

	const time_t StringUtil::parseTime(String timeStr)
	{
		time_t t = 0;

		unsigned int year, month, day, hour, minute, second;

		if (timeStr.length() == 14)
		{
			year = std::stoi(timeStr.substr(0,4));
			month = std::stoi(timeStr.substr(4, 2));
			day = std::stoi(timeStr.substr(6, 2));
			hour = std::stoi(timeStr.substr(8, 2));
			minute = std::stoi(timeStr.substr(10, 2));
			second = std::stoi(timeStr.substr(12, 2));
		}
		else
		{
			StringVector sv = StringUtil::split(timeStr);

			if (sv.size() != 2)
			{
				return t;
			}

			String yyyymmdd = sv[0];
			String hhmmss = sv[1];

			StringVector sv1 = StringUtil::split(yyyymmdd, "-");
			if (sv1.size() != 3)
			{
				return t;
			}
			StringVector sv2 = StringUtil::split(hhmmss, ":");
			if (sv2.size() != 3)
			{
				return t;
			}

			year = std::stoi(sv1[0]);
			month = std::stoi(sv1[1]);
			day = std::stoi(sv1[2]);
			hour = std::stoi(sv2[0]);
			minute = std::stoi(sv2[1]);
			second = std::stoi(sv2[2]);
		}

		struct tm gmtm;
		memset(&gmtm, 0, sizeof(tm));
		gmtm.tm_year = year - 1900;
		gmtm.tm_mon = month - 1;
		gmtm.tm_mday = day;
		gmtm.tm_hour = hour;
		gmtm.tm_min = minute;
		gmtm.tm_sec = second;

		t = std::mktime(&gmtm);
		if (t == -1) t = 0;
		return t;
	}
}
